package jp.tkym.labs.rpc;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;


/**
 * 
 * @author takayama
 */
public class RpcClientProxy{
	private RpcServiceProxyProvider rpcServiceProxyProvider;
	private URL url;
	private Executor executor = Executors.newSingleThreadExecutor();
	
	public RpcClientProxy(URL url) {
		this.url = url;
		InvocationHandler handler = createInvocationHandler(); 
		rpcServiceProxyProvider = new RpcServiceProxyProvider(handler);
	}
	
	private InvocationHandler createInvocationHandler(){
		return new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
				Callable<Object> callable = new Callable<Object>() {
					@Override
					public Object call() throws Exception {
						RpcBean bean = new RpcBean(method);
						bean.setParameters(args);
						RpcBeanClientProcesser processer 
							= new RpcBeanClientProcesser(url);
						processer.processRpcBean(bean);
						if(bean.getException() != null)
							throw bean.getException();
						for(int i=0; i<args.length; i++)
							args[i] = bean.getParameters()[i];
						return bean.getResult();
					}
				};
				FutureTask<Object> futureTask = new FutureTask<Object>(callable);
				executor.execute(futureTask);
				return futureTask.get();
			}
		};
	}
	
	public <SV> SV getService(Class<SV> cls) {
		return rpcServiceProxyProvider.get(cls);
	}

	class RpcBeanClientProcesser{
		private URL url;
		
		RpcBeanClientProcesser(URL url) throws MalformedURLException{
			this.url = url;
		}
		
		void processRpcBean(RpcBean bean) throws IOException, ClassNotFoundException{
			URLConnection connection = url.openConnection();
			connection.setDoOutput(true);
			connection.setDoInput(true);
			ObjectOutputStream oos = 
					new ObjectOutputStream(
							connection.getOutputStream());
			oos.writeObject(bean);
			ObjectInputStream ois = 
					new ObjectInputStream(
							connection.getInputStream());
			RpcBean result = (RpcBean) ois.readObject();
			bean.setResult(result.getResult());
			bean.setParameters(result.getParameters());
			bean.setException(result.getException());
		}
	}

	class RpcServiceProxyProvider{
		private final InvocationHandler handler;
		private Map<Class<?>, Object> proxyMap = new ConcurrentHashMap<Class<?>, Object>();
		
		RpcServiceProxyProvider(InvocationHandler handler){
			this.handler = handler;
		};
		
		@SuppressWarnings("unchecked")
		<SV> SV get(Class<SV> cls){
			SV service = (SV) proxyMap.get(cls);
			if(service == null){
				service = create(cls);
				proxyMap.put(cls, service);
			} 
			return service;
		}
		
		@SuppressWarnings("unchecked")
		<SV> SV create(Class<SV> cls){
			ClassLoader loader = cls.getClassLoader();
			Class<?>[] interfaces = new Class<?>[]{cls};
			Object proxyObject = 
					Proxy.newProxyInstance(loader, interfaces, handler);
			return (SV) proxyObject;
		}
	}
}